package de.fzi.kasma.learner.function.kernel.rdf;

import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import com.sleepycat.je.Cursor;
import com.sleepycat.je.CursorConfig;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseConfig;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.Environment;
import com.sleepycat.je.EnvironmentConfig;
import com.sleepycat.je.EnvironmentLockedException;
import com.sleepycat.je.EnvironmentNotFoundException;
import com.sleepycat.je.LockMode;
import com.sleepycat.je.OperationStatus;
import com.sleepycat.je.VersionMismatchException;

import edu.unika.aifb.graphindex.index.IndexDirectory;
import edu.unika.aifb.graphindex.util.Util;

public class GraphMap {

	private Database m_nodeIdMapDb;
	private Database m_idNodeMapDb;
	private Database m_edgeIdMapDb;
	private Database m_idEdgeMapDb;
	private Database m_sourceEdgeDb;
	private Database m_edgeTargetDb;
	private Database m_edgeSourceDb;

	private Map<Integer, String> nodes;
	private Map<Integer, String> edges;

	private static int nodeCounter = 0;
	private static int edgeCounter = 0;

	public GraphMap(File cacheDir) throws DatabaseException, IOException {
		EnvironmentConfig config = new EnvironmentConfig();
		config.setTransactional(false);
		config.setAllowCreate(true);

		Environment env = new Environment(cacheDir, config);
		initialize(env);
	}

	/**
	 * Initializes the databases.
	 * 
	 * @param env
	 */
	private void initialize(Environment env) {
		DatabaseConfig config = new DatabaseConfig();
		config.setTransactional(false);
		config.setAllowCreate(true);
		config.setSortedDuplicates(false);
		config.setDeferredWrite(true);
		// The assigned id of a node will be hold here.Duplicate entries are not
		// allowed.
		m_nodeIdMapDb = env.openDatabase(null, "nidc", config);

		config.setTransactional(false);
		config.setAllowCreate(true);
		config.setSortedDuplicates(false);
		config.setDeferredWrite(true);
		// The assigned node of an id will be hold here.Duplicate entries are
		// not allowed.
		m_idNodeMapDb = env.openDatabase(null, "idnc", config);

		config.setTransactional(false);
		config.setAllowCreate(true);
		config.setSortedDuplicates(false);
		config.setDeferredWrite(true);
		// The assigned id of an edge will be hold here.Duplicate entries are
		// not allowed.
		m_edgeIdMapDb = env.openDatabase(null, "eidc", config);

		config.setTransactional(false);
		config.setAllowCreate(true);
		config.setSortedDuplicates(false);
		config.setDeferredWrite(true);
		// The assigned edge of an id will be hold here.Duplicate entries are
		// not allowed.
		m_idEdgeMapDb = env.openDatabase(null, "idec", config);

		config.setTransactional(false);
		config.setAllowCreate(true);
		config.setSortedDuplicates(true);
		config.setDeferredWrite(true);
		// The edge id of a source will be hold here.Duplicate entries are
		// allowed.
		m_sourceEdgeDb = env.openDatabase(null, "sec", config);

		config.setTransactional(false);
		config.setAllowCreate(true);
		config.setSortedDuplicates(true);
		config.setDeferredWrite(true);
		// The target id of an edge will be hold here.Duplicate entries are
		// allowed.
		m_edgeTargetDb = env.openDatabase(null, "tec", config);

		config.setTransactional(false);
		config.setAllowCreate(true);
		config.setSortedDuplicates(true);
		config.setDeferredWrite(true);
		// The edge id of a source will be hold here.Duplicate entries are
		// allowed.
		m_edgeSourceDb = env.openDatabase(null, "esc", config);

		nodes = new HashMap<Integer, String>();
		edges = new HashMap<Integer, String>();
	}

	/**
	 * Closes the databeses.
	 */
	public void close() {
		m_edgeIdMapDb.close();
		m_idEdgeMapDb.close();
		m_nodeIdMapDb.close();
		m_idNodeMapDb.close();
		m_sourceEdgeDb.close();
		m_edgeTargetDb.close();
	}

	/**
	 * This function adds a node information to the database by assigning a new
	 * id.It returns the assigned id.
	 * 
	 * @param node
	 *            This is a node in the graph.
	 * @return The assigned id to the node.
	 */
	public Integer addNode(String node) {
		try {
			m_nodeIdMapDb.put(null, new DatabaseEntry(node.getBytes()),
					new DatabaseEntry(Util.intToBytes(nodeCounter)));
			m_idNodeMapDb.put(null,
					new DatabaseEntry(Util.intToBytes(nodeCounter)),
					new DatabaseEntry(node.getBytes()));

			nodes.put(nodeCounter, node);
			nodeCounter++;
			return nodeCounter - 1;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * This function adds an edge information to the database by assigning a new
	 * id. It returns the new id assigned.
	 * 
	 * @param edge
	 *            This is a concentaneted string description of a edge triple as
	 *            Subj_Pred_Obj
	 * @return The new id assigned to the edge
	 */
	public Integer addEdge(String edge) {
		try {
			m_edgeIdMapDb.put(null, new DatabaseEntry(edge.getBytes()),
					new DatabaseEntry(Util.intToBytes(edgeCounter)));
			m_idEdgeMapDb.put(null,
					new DatabaseEntry(Util.intToBytes(edgeCounter)),
					new DatabaseEntry(edge.getBytes()));
			edges.put(edgeCounter, edge);
			edgeCounter++;
			return edgeCounter - 1;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * This function adds a edge id to the database using the source id as a
	 * key.
	 * 
	 * @param source
	 *            This is the source node of the edge.
	 * @param edge
	 *            This is an edge in the graph.
	 */
	public void addSubjId2EdgeId(Integer source, Integer edge) {
		try {
			m_sourceEdgeDb.putNoDupData(null,
					new DatabaseEntry(Util.intToBytes(source)),
					new DatabaseEntry(Util.intToBytes(edge)));
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * This function adds a target id to the database using the edge id as a
	 * key.
	 * 
	 * @param edge
	 *            This is an edge in the graph.
	 * @param target
	 *            This is the target node of the edge.
	 */
	public void addEdgeId2ObjId(Integer edge, Integer target) {
		try {
			m_edgeTargetDb.putNoDupData(null,
					new DatabaseEntry(Util.intToBytes(edge)),
					new DatabaseEntry(Util.intToBytes(target)));
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * This function adds a source id to the database using the edge id as a
	 * key.
	 * 
	 * @param edgeId
	 *            This is an edge in the graph.
	 * @param subjId
	 *            This is the source node of the edge.
	 */
	public void addEdgeId2SubjId(Integer edgeId, Integer subjId) {
		try {
			m_edgeSourceDb.putNoDupData(null,
					new DatabaseEntry(Util.intToBytes(edgeId)),
					new DatabaseEntry(Util.intToBytes(subjId)));
		} catch (Exception e) {
			e.printStackTrace();
		}

	}

	/**
	 * This function returns the assigned id of a node using the
	 * m_nodeIdMapDb.The node is the key of the database.
	 * 
	 * @param node
	 *            This is a key in the database.
	 * @return assigned id of the node.
	 */
	public Integer getNodeId(String node) {
		try {
			DatabaseEntry out = new DatabaseEntry();
			m_nodeIdMapDb.get(null, new DatabaseEntry(node.getBytes()), out,
					null);
			if (out.getData() == null) {
				return null;
			}
			return Util.bytesToInt(out.getData());
		} catch (DatabaseException e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * This function returns the assigned node of the id using the
	 * m_idNodeMapDb.The id is the key of the database.
	 * 
	 * @param id
	 *            This is a key in the database.
	 * @return String result = assigned node of the id
	 * @throws UnsupportedEncodingException
	 */
	public String getIdNode(Integer id) throws UnsupportedEncodingException {
		try {
			DatabaseEntry out = new DatabaseEntry();
			m_idNodeMapDb.get(null, new DatabaseEntry(Util.intToBytes(id)),
					out, null);
			if (out.getData() == null) {
				return null;
			}
			String result = new String(out.getData(), "UTF-8");
			return result;
		} catch (DatabaseException e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * This function returns the assigned id of an edge using the
	 * m_edgeIdMapDb.The String edge is the key of the database.
	 * 
	 * @param edge
	 *            This is a key in the database.
	 * @return assigned id of the edge
	 */
	public Integer getEdgeId(String edge) {
		try {
			DatabaseEntry out = new DatabaseEntry();
			m_edgeIdMapDb.get(null, new DatabaseEntry(edge.getBytes()), out,
					null);
			if (out.getData() == null) {
				return null;
			}
			return Util.bytesToInt(out.getData());
		} catch (DatabaseException e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * This function returns the assigned edge of an id using the m_idEdgeMapDb.
	 * The Integer id is the key of the database.
	 * 
	 * @param id
	 *            This is a key in the database.
	 * @return assigned edge of the id.
	 * @throws UnsupportedEncodingException
	 */
	public String getIdEdge(Integer id) throws UnsupportedEncodingException {
		try {
			DatabaseEntry out = new DatabaseEntry();
			m_idEdgeMapDb.get(null, new DatabaseEntry(Util.intToBytes(id)),
					out, null);
			if (out.getData() == null) {
				return null;
			}
			String result = new String(out.getData(), "UTF-8");
			return result;
		} catch (DatabaseException e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * This function returns a set of edge informations that is related with the
	 * subject node using the m_sourceEdgeDb.
	 * 
	 * @param subj
	 *            This is a source node id of an edge.
	 * @param edge
	 *            This is a edge id.
	 * @return Set<Integer> edgeIds = possible edge informations related with
	 *         the subject.
	 */
	public Set<Integer> getSubjEdgeIds(Integer subj) {
		CursorConfig config = new CursorConfig();
		config.setReadUncommitted(true);
		Cursor cursor = m_sourceEdgeDb.openCursor(null, null);
		Set<Integer> edgeIds = new HashSet<Integer>();

		DatabaseEntry foundKey = new DatabaseEntry(Util.intToBytes(subj));
		DatabaseEntry out = new DatabaseEntry();
		try {
			while (cursor.getNext(foundKey, out, LockMode.DEFAULT) == OperationStatus.SUCCESS) {
				Integer id = Util.bytesToInt(out.getData());
				edgeIds.add(id);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return edgeIds;
	}

	/**
	 * This function returns target information that is related with the edge
	 * using the m_edgeTargetDb.
	 * 
	 * @param edge
	 *            This is a edge information.
	 * @param edge
	 *            This is a target node information of an edge
	 * @return Integer id = possible object information related with the edge.
	 */
	public Integer getEdgeObjIds(Integer edge) {
		try {
			DatabaseEntry out = new DatabaseEntry();
			m_edgeTargetDb.get(null, new DatabaseEntry(Util.intToBytes(edge)),
					out, null);
			if (out.getData() == null) {
				return null;
			}
			return Util.bytesToInt(out.getData());
		} catch (DatabaseException e) {
			e.printStackTrace();
		}
		return null;
	}

	public Integer getEdgeSubjIds(Integer edge) {
		try {
			DatabaseEntry out = new DatabaseEntry();
			m_edgeSourceDb.get(null, new DatabaseEntry(Util.intToBytes(edge)),
					out, null);
			if (out.getData() == null) {
				return null;
			}
			return Util.bytesToInt(out.getData());
		} catch (DatabaseException e) {
			e.printStackTrace();
		}
		return null;
	}

	public Set<Integer> getNodes() {
		return nodes.keySet();
	}

	public Set<Integer> getEdges() {
		return edges.keySet();
	}
}